#include "ObliqueConfig.h"
#include "ObliqueUtil.h"

using namespace OCPP;
using namespace OCPP::Cesium;

inline double computeDistance(double lon1, double lat1, double lon2, double lat2)
{
	EllipsoidGeodesic eGeodesic(Cartographic(lon1, lat1),
		Cesium::Cartographic(lon2, lat2), Ellipsoid::WGS84);
	return eGeodesic.getDistance();
}

double ObliqueConfig::computeGE(uint32 L)
{
	if (startTileLevel > 9)
	{
#if 0
		auto west = CesiumMath::toRadians(region.west);
		auto south = CesiumMath::toRadians(region.south);
		auto east = CesiumMath::toRadians(region.east);
		auto north = CesiumMath::toRadians(region.north);

		double distance0 = computeDistance(0.0, south, CesiumMath::PI_OVER_TWO, south) * 4.0;

		GeographicTilingScheme tilingScheme;
		int nx = tilingScheme.getNumberOfXTilesAtLevel(L);

		auto tileID = tilingScheme.positionToTileXY({ (float)west,(float)south }, L);
		auto tileRectangle = tilingScheme.tileXYToRectangle(tileID[0], tileID[1], worldLevel);
		double distance2 = computeDistance(tileRectangle.west, tileRectangle.south, tileRectangle.east, tileRectangle.south);
#endif
		double geometryError = 1024.0;
		while (L-- > 10)
		{
			geometryError *= 0.5;
		}
		// 10 - 1024
		// 11 - 512
		// 12 - 256
		// 13 - 128
		// 14 - 64
		// 15 - 32
		return geometryError;
	}
	else
	{
		return  boundingBox.radius();
	}

}

uint32 ObliqueConfig::getOverlapTileNum(uint32 L)
{
	auto rectangle = Cesium::Rectangle(
		Cesium::CesiumMath::toRadians(region.west),
		Cesium::CesiumMath::toRadians(region.south),
		Cesium::CesiumMath::toRadians(region.east),
		Cesium::CesiumMath::toRadians(region.north));
	GeographicTilingScheme tilingScheme;
	auto ws = Cesium::Rectangle::southwest(rectangle);
	auto en = Cesium::Rectangle::northeast(rectangle);

	auto start = tilingScheme.positionToTileXY({ (float)ws.longitude,(float)ws.latitude }, L);
	auto end = tilingScheme.positionToTileXY({ (float)en.longitude,(float)en.latitude }, L);

	uint32 numX = end.x - start.x + 1;
	uint32 numY = start.y - end.y + 1;

	return numX * numY;
}

Json::Value ObliqueConfig::toJson()
{
	Json::Value value;

	value["dataPath"] = dataPath;
	value["numTiles"] = numTiles;
	value["nodeDepth"] = nodeDepth;
	value["startTileLevel"] = startTileLevel;
	value["endTileLevel"] = endTileLevel;
	
	value["srs"] = srs;
	value["origin"] = StringConverter::toString(origin);
	value["location"] = StringConverter::toString(location);
	value["boundingBox"] = box2string(boundingBox);

	auto center = Cesium::Cartesian3::fromDegrees(location.x, location.y, location.z);
	auto matrix = Cesium::Transforms::eastNorthUpToFixedFrame(center);

	auto westSouth = Cesium::Matrix4::multiplyByPoint(matrix, Cesium::Cartesian3(boundingBox.xMin(), boundingBox.yMin(), boundingBox.zMin()));
	auto eastNorth = Cesium::Matrix4::multiplyByPoint(matrix, Cesium::Cartesian3(boundingBox.xMax(), boundingBox.yMax(), boundingBox.zMax()));

	auto ws = Cesium::Cartographic::fromCartesian(westSouth);
	auto en = Cesium::Cartographic::fromCartesian(eastNorth);

	region = Cesium::Rectangle(
		Cesium::CesiumMath::toDegrees(ws.longitude),
		Cesium::CesiumMath::toDegrees(ws.latitude),
		Cesium::CesiumMath::toDegrees(en.longitude),
		Cesium::CesiumMath::toDegrees(en.latitude));

	value["region"] = region.toString();

	Json::Value numPixelPerLevelsObj;
	for (auto& num : numPixelPerLevels)
	{
		numPixelPerLevelsObj.append(uint64(num));
	}
	value["numPixelPerLevels"] = numPixelPerLevelsObj;

	return value;
}

void ObliqueConfig::fromJson(Json::Value& value)
{
	dataPath = U2A(value["dataPath"].asString());
	numTiles = value["numTiles"].asUInt();
	nodeDepth = value["nodeDepth"].asUInt();
	startTileLevel = value["startTileLevel"].asUInt();
	endTileLevel = value["endTileLevel"].asUInt();
	String regionStr = value["region"].asString();
	auto regionVec = StringUtil::split(regionStr, ",");
	if (regionVec.size() == 4)
	{
		region.west = std::stof(regionVec[0]);
		region.south = std::stof(regionVec[1]);
		region.east = std::stof(regionVec[2]);
		region.north = std::stof(regionVec[3]);
	}

	srs = value["srs"].asString();
	origin = StringConverter::parseVector3(value["origin"].asString());
	location = StringConverter::parseVector3(value["location"].asString());
	String boxStr = value["boundingBox"].asString();
	boundingBox = str2box(boxStr);

	Json::Value numPixelPerLevelsObj = value["numPixelPerLevels"];
	for (auto i = 0; i < numPixelPerLevelsObj.size(); i++)
	{
		numPixelPerLevels.push_back(numPixelPerLevelsObj[i].asUInt64());
	}
}

void ObliqueConfig::countPixelsPerLevel(LongVector pixelsPerLevel)
{
	if (numPixelPerLevels.size() < pixelsPerLevel.size())
	{
		numPixelPerLevels.resize(pixelsPerLevel.size());
	}
	for (auto i = 0; i < pixelsPerLevel.size(); i++)
	{
		numPixelPerLevels[i] += pixelsPerLevel[i];
	}
}

void ObliqueConfig::setStartTileLevel(uint32 level)
{
	startTileLevel = level;
}

